/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package gride.project;

import gride.GrideSettings;
import gride.events.EventListener;
import gride.events.EventManager;
import gride.events.GrideEvents;
import gride.exceptions.UserCancelException;
import gride.groovy.Regex;
import gride.groovy.Utils;

import gride.gui.AddFileForm;
import gride.gui.GUIManager;
import gride.handlers.AddFileHandler;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;

/**
 *
 * @author lukasz
 */
public class ProjectManager
{

    // <editor-fold defaultstate="expanded" desc="Members">

    GUIManager guiManager;
    EventManager eventManager;

    Project currentProject;

    // file we're currently 'working on'
    GrideScript activeFile = null;

    
    // gui components

    // entries in File menu
    JMenuItem newProjectMenuItem;
    JMenuItem openProjectMenuItem;
    JMenuItem closeProjectMenuItem;
    
    // entries in Project menu
    JMenu projectMenu;
    JMenuItem addScriptMenuItem;
    JMenuItem importScriptMenuItem;
    JMenuItem importLibraryMenuItem;
    JMenuItem xmlMenuItem;

    // </editor-fold>


    // <editor-fold defaultstate="expanded" desc="Constructors">

    /**
     *
     * @param eventManager
     * @param guiManager
     */
    public ProjectManager(EventManager eventManager, GUIManager guiManager)
    {
        this.eventManager = eventManager;
        this.guiManager = guiManager;

        createGUIComponents();

        // make sure the projects directory exists
        new File(GrideSettings.PROJECTS_DIR).mkdirs();

        final ProjectManager myself = this;

        // add a listener waiting for EXITING_GRIDE event
        eventManager.addListener(GrideEvents.EXITING_GRIDE, new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                if (currentProject != null)                
                    try
                    {
                        currentProject.saveProject();
                    }
                    catch (IOException e)
                    {
                        int answer = JOptionPane.showConfirmDialog(null,
                                "Error saving project. Exit anyway?", "Error",
                                JOptionPane.YES_NO_OPTION,
                                JOptionPane.WARNING_MESSAGE);

                        if (answer == JOptionPane.NO_OPTION)
                            throw new UserCancelException();
                    }
            }
        });

        // register listener for project_remove_file event
        eventManager.addListener(GrideEvents.PROJECT_EXCLUDE_FILE,
                                 new EventListener()
        {
            public Object whoIsListening()
            {
                return myself;
            }

            public void eventRaised(String eventID, Object[] args)
            {
                if (currentProject != null)
                {
                    String arg0 = (String) args[0];

                    if (arg0.equals("script"))
                        currentProject.excludeScript((GrideScript)args[1]);
                    else if (arg0.equals("library"))
                        currentProject.excludeLibrary((GrideLibrary)args[1]);

                    myself.eventManager.raiseEvent(
                            GrideEvents.PROJECT_FILE_EXCLUDED, args);

                    currentProject.trySaveProject();
                }
            }
        });
    }

    // </editor-fold>


    // <editor-fold defaultstate="expanded" desc="Public Methods">

    /**
     *
     * @return
     */
    public GrideScript getActiveFile()
    {
        return activeFile;
    }

    /**
     * 
     * @param file
     */
    public void setActiveFile(GrideScript file)
    {
        this.activeFile = file;

        // raise an event notifying of changed active file
        Object[] args = { file };
        eventManager.raiseEvent(GrideEvents.ACTIVE_FILE_CHANGED, args);
    }



    /**
     *
     * @return
     */
    public Project getCurrentProject()
    {
        return currentProject;
    }

    /**
     *
     * @return
     */
    public List<String> getExistingProjectNames()
    {
        List<String> list = new ArrayList<String>();

        // look into the PROJECTS_DIR directory and find existing projects
        for (File dir : new File(GrideSettings.PROJECTS_DIR).listFiles())
            if (dir.isDirectory())
            {
                // check if the directory contains a <project_name>.gri file
                for (File file : dir.listFiles())
                    if (file.getName().equals(dir.getName() + ".gri"))
                    {
                        // project file found - add project name to list
                        list.add(dir.getName());
                        break;
                    }
            }

        return list;
    }

    
    /**
     *
     * @param projectName
     */
    public void openProjectNamed(String projectName)
    {
        File file = new File(GrideSettings.PROJECTS_DIR + "/" + projectName +
                             "/" + projectName + ".gri");

        if (file.exists() && file.isFile())
            openProject(file);
    }

    // </editor-fold>


    // <editor-fold defaultstate="expanded" desc="GUI component">

    private void createGUIComponents()
    {
        newProjectMenuItem = new JMenuItem("New project...");
        newProjectMenuItem.setAccelerator(
                javax.swing.KeyStroke.getKeyStroke(
                        java.awt.event.KeyEvent.VK_N,
                        java.awt.event.InputEvent.CTRL_MASK));

        newProjectMenuItem.addActionListener(new ActionListener()
        {

            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    newProjectAction();
                }
                catch (UserCancelException ex)
                {
                    System.out.println("User cancelled creating new project");
                }
            }
        });

        openProjectMenuItem = new JMenuItem("Open project...");
        openProjectMenuItem.setAccelerator(
                javax.swing.KeyStroke.getKeyStroke(
                        java.awt.event.KeyEvent.VK_O,
                        java.awt.event.InputEvent.CTRL_MASK));

        openProjectMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    openProjectAction();
                }
                catch (UserCancelException ex)
                {
                    System.out.println("User cancelled opening a project");
                }
            }
        });

        closeProjectMenuItem = new JMenuItem("Close project");
        closeProjectMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    closeProjectAction();
                }
                catch (UserCancelException ex)
                {
                    System.out.println("User cancelled closing the current " +
                                       "project");
                }
            }
        });
        closeProjectMenuItem.setEnabled(false);

        addScriptMenuItem = new JMenuItem("New script...");
        addScriptMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                addScriptAction();
            }
        });
        addScriptMenuItem.setEnabled(false);



        importScriptMenuItem = new JMenuItem("Import script...");
        importScriptMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                importScriptAction();
            }
        });
        importScriptMenuItem.setEnabled(false);

        importLibraryMenuItem = new JMenuItem("Import library...");
        importLibraryMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                importLibraryAction();
            }
        });
        importLibraryMenuItem.setEnabled(false);

        xmlMenuItem = new JMenuItem("Show project XML...");
        xmlMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                xmlAction();
            }
        });
        xmlMenuItem.setEnabled(false);

        guiManager.addMenuItem(newProjectMenuItem, "File");
        guiManager.addMenuItem(openProjectMenuItem, "File");
        guiManager.addMenuItem(closeProjectMenuItem, "File");

        guiManager.addMenuItem(addScriptMenuItem, "Project");
        guiManager.addMenuItem(importScriptMenuItem, "Project");
        guiManager.addMenuItem(importLibraryMenuItem, "Project");
        guiManager.addMenuItem(xmlMenuItem, "Project");
    }
    
    /**
     *
     * @return
     */
    public GrideScript getCurrentMainScript()
    {
        if (currentProject != null)
            return currentProject.getMainScript();
        else
            return null;
    }

    /**
     *
     * @param script
     */
    public void setCurrentMainScript(GrideScript script)
    {
        if (currentProject != null)
        {
            currentProject.setMainScript(script);

            Object[] args = { script };
            eventManager.raiseEvent(GrideEvents.MAIN_SCRIPT_CHANGED, args);
        }
    }

    // </editor-fold>


    // <editor-fold defaultstate="expanded" desc="Private Methods">

    /**
     *
     */
    public void newProjectAction()
    {        
        String projectName = JOptionPane.showInputDialog("Enter project name:");

        if (projectName != null)
        {
            if (!Regex.validateProjectName(projectName))
            {
                JOptionPane.showMessageDialog(null,
                       "Project name can only contain alphanumeric characters!",
                       "Ivalid Name - " +
                       projectName, JOptionPane.ERROR_MESSAGE);

                return;
            }

            // check if a project with this name already exists
            if (new File(GrideSettings.PROJECTS_DIR + "/" +
                         projectName).exists())
            {
                int answer = JOptionPane.showConfirmDialog(null,
                        "A directory corresponding to this project name has " +
                        "been found, probably containing files of a " +
                        "previously created project.\n Creating a new " +
                        "project with this name will delete all files in " +
                        "this directory. Do you wish to proceed?",
                        "Project may already exist",
                        JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

                if (answer == JOptionPane.NO_OPTION)
                    throw new UserCancelException();
            }

            // create a new project
            Project newProject = null;
            try
            {
                newProject = new Project(projectName);
            }
            catch (Exception ex)
            {
                JOptionPane.showMessageDialog(null, "Error: " +
                                                    ex.getMessage());
                return;
            }

            // close the existing project
            if (currentProject != null)
                closeProjectAction();

            // set the new project as current
            setCurrentProject(newProject);

            // notify about loading the project
            eventManager.raiseEvent(GrideEvents.PROJECT_LOADED, null);
        }
    }


    private void openProjectAction()
    {
        FileNameExtensionFilter filter = new FileNameExtensionFilter(
                                                  "Gride project files", "gri");

        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setCurrentDirectory(new File(GrideSettings.PROJECTS_DIR));
        fileChooser.setFileFilter(filter);

        if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)
            openProject(fileChooser.getSelectedFile());
    }


    private void openProject(File projectFile)
    {
        // load the selected project
            Project loadedProject = null;
            try
            {
                loadedProject = Project.loadProject(projectFile);
            }
            catch (Exception ex)
            {
                JOptionPane.showMessageDialog(null, "Error: " +
                                                    ex.getMessage());
                return;
            }

            // close the existing project
            if (currentProject != null)
                closeProjectAction();

            setCurrentProject(loadedProject);

            // notify about loading the project
            eventManager.raiseEvent(GrideEvents.PROJECT_LOADED, null);
    }


    private void closeProjectAction()
    {
        if (currentProject != null)
        {
            eventManager.raiseEvent(GrideEvents.CLOSING_PROJECT, null);

            try
            {
                currentProject.saveProject();
            }
            catch (IOException e)
            {
                int answer = JOptionPane.showConfirmDialog(null,
                        "Couldn't save existing project. Continue?", "Warning",
                        JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

                if (answer == JOptionPane.NO_OPTION)
                    throw new UserCancelException();
            }

            setCurrentProject(null);

            // notify about closing the project
            eventManager.raiseEvent(GrideEvents.PROJECT_CLOSED, null);
        }
    }
    
    
    private void setCurrentProject(Project project)
    {        
        currentProject = project;
        
        // enable/disable project-specific menu items
        if (currentProject == null)
        {
            closeProjectMenuItem.setEnabled(false);
            addScriptMenuItem.setEnabled(false);
            importScriptMenuItem.setEnabled(false);
            importLibraryMenuItem.setEnabled(false);
            xmlMenuItem.setEnabled(false);
        }
        else
        {
            closeProjectMenuItem.setEnabled(true);
            addScriptMenuItem.setEnabled(true);
            importScriptMenuItem.setEnabled(true);
            importLibraryMenuItem.setEnabled(true);
            xmlMenuItem.setEnabled(true);
        }
    }



    private void addScriptAction()
    {
        if (currentProject != null)
        {
            AddFileForm form = new AddFileForm(
                                        currentProject.getCompletePackageList(),
                                        new AddFileHandler()
            {
                public void addFile(String fullName) 
                {
                    GrideScript newScript = new GrideScript(fullName);

                    try
                    {
                        // check if this script isn't already
                        // part of the project
                        for (GrideScript s : currentProject.getScripts())
                            if (s.getFullName().equals(newScript.getFullName()))
                            {
                                int answer = JOptionPane.showConfirmDialog(null,
                                "This script is already part of the project. " +
                                "Overwrite?", "Confirm Overwrite",
                                JOptionPane.YES_NO_OPTION,
                                JOptionPane.QUESTION_MESSAGE);

                                if (answer == JOptionPane.NO_OPTION)
                                    return;

                                // user chose to overwrite - send close file
                                // event and remove previous class
                                Object[] args = { s };
                                eventManager.raiseEvent(GrideEvents.CLOSE_FILE,
                                                        args);

                                currentProject.excludeScript(s);

                                break;
                            }

                        // set the source file of this script
                        File f = new File(currentProject.getSourceDir() + "/" +
                                 newScript.getFullName().replace(".", "/") +
                                 ".groovy");

                        // delete any file at this location
                        if (f.exists())
                            f.delete();

                        newScript.setSourceFile(f);

                        currentProject.addScript(newScript);
                        currentProject.trySaveProject();

                        // notify about adding the script
                        Object[] args = { newScript };
                        eventManager.raiseEvent(GrideEvents.PROJECT_NEW_SCRIPT,
                                                args);

                        // request opening the file for editing
                        Object[] args2 = { newScript };
                        eventManager.raiseEvent(GrideEvents.OPEN_FILE, args2);
                    }
                    catch (IOException ex)
                    {
                        JOptionPane.showMessageDialog(null,
                             "Error creating file " + newScript.getSourcefile(),
                             "Error", JOptionPane.ERROR_MESSAGE);

                        System.out.println("Error: " + ex.getMessage());
                    }
                    catch (UserCancelException ex)
                    {
                        return;
                    }
                }
            });

            form.pack();
            form.setVisible(true);
        }
    }


    

    private void importScriptAction()
    {
        if (currentProject != null)
        {
            FileNameExtensionFilter filter =
                    new FileNameExtensionFilter("Groovy and Java source files",
                                                "groovy", "java");

            JFileChooser fileChooser = new JFileChooser();
            fileChooser.setCurrentDirectory(
                                          new File(GrideSettings.PROJECTS_DIR));
            fileChooser.setFileFilter(filter);

            if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)
            {
                try
                {
                    File f = fileChooser.getSelectedFile();

                    // get the name of the script without .groovy extension
                    String scriptName = f.getName().substring(
                                               0, f.getName().lastIndexOf("."));

                    // read contents and parse package specified
                    String packageName = 
                            Regex.getPackageNameFromSource(readFileContents(f));

                    // create a GrideScript object for this script
                    GrideScript script = new GrideScript(scriptName,
                                                         packageName);

                    // check if this script isn't already part of the project
                    for (GrideScript s : currentProject.getScripts())
                        if (s.getFullName().equals(script.getFullName()))
                        {
                            int answer = JOptionPane.showConfirmDialog(null,
                            "This script is already part of the project. " +
                            "Overwrite?", "Confirm Overwrite",
                            JOptionPane.YES_NO_OPTION,
                            JOptionPane.QUESTION_MESSAGE);

                            if (answer == JOptionPane.NO_OPTION)
                                return;

                            // user chose to overwrite - send close file
                            // event and remove previous class
                            Object[] args = { s };
                            eventManager.raiseEvent(GrideEvents.CLOSE_FILE,
                                                    args);

                            currentProject.excludeScript(s);
                            break;
                        }

                    // copy the file to the source directory
                    File g = new File(currentProject.getSourceDir() + "/" +
                                      script.getFullName().replace(".", "/") +
                                      ".groovy");

                    // delete any file with the same path & name
                    if (g.exists())
                        g.delete();

                    script.setSourceFile(g);
                    Utils.copyFile(f, g);

                    currentProject.addScript(script);
                    currentProject.trySaveProject();

                    // notify about adding the script
                    Object[] args = { "script", script};
                    eventManager.raiseEvent(GrideEvents.PROJECT_FILE_IMPORTED,
                                            args);

                    // request opening the file for editing
                    Object[] args2 = { script };
                    eventManager.raiseEvent(GrideEvents.OPEN_FILE, args2);

                }
                catch (IOException e)
                {
                    JOptionPane.showMessageDialog(null, "Error reading file!",
                                            "Error", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                catch (UserCancelException ex)
                {
                    return;
                }

            }

        }
    }




    private void importLibraryAction()
    {
        if (currentProject != null)
        {
            FileNameExtensionFilter filter =
                    new FileNameExtensionFilter("Jar files", "jar");

            JFileChooser fileChooser = new JFileChooser();
            fileChooser.setCurrentDirectory(
                                          new File(GrideSettings.PROJECTS_DIR));
            fileChooser.setFileFilter(filter);

            if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)
            {
                File f = fileChooser.getSelectedFile();

                // check if this library isn't already in the project
                for (GrideLibrary lib : currentProject.getLibraries())
                    if (lib.getName().equals(f.getName()))
                    {
                        int answer = JOptionPane.showConfirmDialog(null,
                            "This library is already part of the project. " +
                            "Overwrite?", "Confirm Overwrite",
                            JOptionPane.YES_NO_OPTION,
                            JOptionPane.QUESTION_MESSAGE);

                        if (answer == JOptionPane.NO_OPTION)
                            return;
                                                
                        currentProject.excludeLibrary(lib);

                        break;
                    }

                // the file will be copied to the project's lib directory
                File g = new File(currentProject.getLibDir() + "/" +
                                  f.getName());

                // delete any file with the same path
                if (g.exists())
                    g.delete();

                // copy the file
                if (!Utils.copyFileBinary(f, g))
                {
                    JOptionPane.showMessageDialog(null,
                       "Couldn't copy file to project's library dir!");
                    return;
                }

                GrideLibrary newLib = new GrideLibrary(g);
                currentProject.addLibrary(newLib);
                currentProject.trySaveProject();

                // notify about adding the library
                Object[] args = {"library", newLib};
                eventManager.raiseEvent(GrideEvents.PROJECT_FILE_IMPORTED,
                                        args);
            }
        }
    }



    private void xmlAction()
    {
        if (currentProject != null)
        {
            JEditorPane editor = new JEditorPane();
            editor.setText(currentProject.getXML());

            JFrame xmlFrame = new JFrame();
            xmlFrame.add(editor);
            xmlFrame.pack();
            xmlFrame.setVisible(true);
        }
    }


    /**
     * Returns a String containing the contents of the given file
     * @param file
     * @return
     * @throws java.io.IOException
     */
    private String readFileContents(File file) throws IOException
    {
        StringBuilder fileContents = new StringBuilder();
        char[] buf = new char[1024];

        FileReader reader = new FileReader(file);

        int charsRead = 0;
        while ((charsRead = reader.read(buf)) != -1) {
            fileContents.append(buf, 0, charsRead);
        }

       return fileContents.toString();
    }



    // </editor-fold>
}
